/*
 * #%L
 * java model
 * %%
 * Copyright (C) 2009 - 2011 prefabware.com Stefan Isele
 * %%
 * All Rights Reserved.
 * This program is dual-licensed under the GNU General Public license
 * or a commercial license.
 * You may select to use one or the other of these licenses.
 * To obtain a commercial license please contact Stefan Isele at www.stefan-isele.de
 * Any modifications to this file must keep this entire header intact.
 * Redistribution, except as permitted by the selected license, is prohibited.
 * #L%
 */
package com.prefabware.jmodel.annotation;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.prefabware.commons.CollectionUtil;
import com.prefabware.commons.Condition;
import com.prefabware.commons.Message;
import com.prefabware.commons.collection.Filter;
import com.prefabware.commons.collection.Gettable;
import com.prefabware.jmodel.JType;
import com.prefabware.jmodel.annotation.JAnnotationClass.JAnnotationMethod;
import com.prefabware.jmodel.code.JCodable;
import com.prefabware.jmodel.code.JCodeOptions;
import com.prefabware.jmodel.reference.JReference;
import com.prefabware.jmodel.reference.JReferenceBase;
import com.prefabware.jmodel.reference.JReferencing;

/**
 * an annotation of a class, method or field to create an annotation on a JClass
 * it is base on a {@link JAnnotationClass} it is not and has not a SourceFile,
 * because it will not be written in its own file, but will be written into the
 * class or interface it is no JInterface, because from the JavaModel point of
 * view it is not an interface in its own file
 * 
 * @author Administrator
 * 
 */
public class JAnnotation implements JReferencing, JCodable {

	private JAnnotationClass annotationClass;

	public JAnnotationClass getAnnotationClass() {
		return annotationClass;
	}

	@Override
	public String toString() {
		return asCode(JCodeOptions.SIMPLE_NAMES);
	}

	public JAnnotation(JAnnotationClass annotationClass) {
		super();
		this.annotationClass = annotationClass;
		values = new LinkedHashMap<String, JAnnotationValue<?>>();

	}

	private Map<String, JAnnotationValue<?>> values;
	private JType declaringType;

	/**
	 * creates a new JAnnotationValue<?> with the given name and value and adds
	 * it to {@link JAnnotation#values} this is a convinience method, sufficient
	 * for most cases. it is taken care that for often used types the
	 * {@link JAnnotationValue#getValueAsCode()} is prepared in the right way,
	 * e.g. Strings are double quoted ... if this is not good enough, you have
	 * to create a JAnnotationValue yourself, and add it using
	 * {@link #setValue(JAnnotationValue)}
	 * 
	 * @param name
	 * @param value
	 * @return
	 */
	public JAnnotationValue<?> setValue(String name, Object value) {
		JAnnotationMethod method = this.annotationClass.getAnnotationMethod(name);
		Class<?> valueType;
		if (value==null) {
			valueType=null;
		}else {
			valueType=value.getClass();
		}
		//value may be the wrapper instead of the expected primitive
		//e.g. Integer instead of int
		Condition.notNull(method,
				Message.format("cannot find annotation method with name \'%s\' in %s", name, this.annotationClass));
//		Condition.isTrue(value == null ||value.getClass().isArray()||(value instanceof JAnnotation)
//				||  JavaUtil.isAssignable(method.getType().getValueClazz(),value.getClass()),
//				Message.format("trying to set value with name \'%s\' to %s , but its type is %s and not the required type %s", name,value, valueType,method.getType().getValueClazz()));
		JAnnotationValue<?> annotationValue = method.createValue(name, value);
		setValue(annotationValue);
		return annotationValue;
	}

	/**
	 * sets the AnnotationValue. You may consider to use
	 * {@link #setValue(String, Object)}
	 * 
	 * If the map previously contained a mapping for the key, the old value is
	 * replaced by the specified value. see {@link Map#put(Object, Object)}
	 * 
	 * @param annotationValue
	 * @return
	 */
	public JAnnotationValue<?> setValue(JAnnotationValue<?> annotationValue) {
		return values.put(annotationValue.getName(), annotationValue);
	}

	public Collection<JAnnotationValue<?>> getValues() {
		return values.values();
	}

	/**
	 * the values of the annotation to use in java-code
	 * 
	 * @return the values of the annotation without unset values without default
	 *         values
	 */
	public Collection<JAnnotationValue<?>> getValuesWithoutDefaults() {
		Filter<? super JAnnotationValue<?>> filter = new Filter<JAnnotationValue<?>>() {

			@Override
			public boolean includes(JAnnotationValue<?> value) {
				//null means the value is unset 
				return !getAnnotationClass().isSet(value);
			}
		};
		return CollectionUtil.filter(getValues(), filter);
	}

	/**
	 * @return true, if all AnnotationValues are default
	 */
	public boolean isDefault() {
		for (JAnnotationValue<?> value : this.getValues()) {
			if (!this.getAnnotationClass().isSet(value)) {
				// if one value is not at default, the whole annotation
				// is not at default
				return false;
			}
		}
		return true;
	}

	public JAnnotationValue<?> getAnnotationValue(String name) {
		return values.get(name);
	}

	@Override
	public String asCode(final JCodeOptions options) {
		Gettable<String, JAnnotationValue<?>> gettable = new Gettable<String, JAnnotationValue<?>>() {

			@Override
			public String get(JAnnotationValue<?> from) {
				return from.asCode(options);
			}
		};

		List<String> values = CollectionUtil.getList(gettable, getValuesWithoutDefaults());
		// TODO do we still need this, now that the default is allways known ?
		Collection<String> withoutNull = CollectionUtil.excludeNull(values);
		return "@" + this.getAnnotationClass().asCode(options) + "(" + CollectionUtil.seperatedBy(withoutNull, ",")
				+ ")";
	}

	public List<JReference<?>> getReferences() {
		List<JReference<?>> references = new ArrayList<JReference<?>>();
		references.add(JReferenceBase.create(this.declaringType, this.getAnnotationClass(), this));
		// TODO references to annotation values e.g. classes !
		return references;
	}

	public void setDeclaringType(JType type) {
		this.declaringType = type;

	}

	public JType getDeclaringType() {
		return declaringType;
	}
}
